/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.rest.v1.meta.converter.entities;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.unidata.mdm.meta.context.UpsertDataModelContext;
import org.unidata.mdm.meta.context.UpsertDataModelContext.UpsertDataModelContextBuilder;
import org.unidata.mdm.meta.dto.GetEntityDTO;
import org.unidata.mdm.meta.type.model.attributes.AttributeGroup;
import org.unidata.mdm.meta.type.model.entities.Entity;
import org.unidata.mdm.meta.type.model.entities.NestedEntity;
import org.unidata.mdm.meta.type.model.entities.RelType;
import org.unidata.mdm.meta.type.model.entities.Relation;
import org.unidata.mdm.meta.type.model.entities.RelationGroup;
import org.unidata.mdm.rest.core.converter.CustomPropertiesConverter;
import org.unidata.mdm.rest.v1.meta.converter.ArrayAttributeDefConverter;
import org.unidata.mdm.rest.v1.meta.converter.ExternalIdGenerationStrategyConverter;
import org.unidata.mdm.rest.v1.meta.converter.MergeSettingsConverter;
import org.unidata.mdm.rest.v1.meta.converter.PeriodBoundaryConverter;
import org.unidata.mdm.rest.v1.meta.converter.RelationDefConverter;
import org.unidata.mdm.rest.v1.meta.converter.RelationDefinitionConverter;
import org.unidata.mdm.rest.v1.meta.converter.SimpleAttributeDefConverter;
import org.unidata.mdm.rest.v1.meta.ro.entities.RegisterEntityRO;
import org.unidata.mdm.rest.v1.meta.ro.groups.GroupsRO;
import org.unidata.mdm.rest.v1.meta.ro.relations.RelationGroupsRO;
import org.unidata.mdm.rest.v1.meta.ro.relations.RelationRO;
import org.unidata.mdm.rest.v1.meta.ro.relations.RelationTypeRO;


/**
 * @author Michael Yashin. Created on 26.05.2015.
 */
public class EntityDefinitionConverter extends AbstractEntityDefinitionConverter {

    /**
     * Converts internal representation of an entity - {@link Entity} to a REST capable {@link RegisterEntityRO}.
     *
     * @param input {@link GetEntityDTO} object
     * @return REST capable entity definition
     */
    public static RegisterEntityRO toRegisterEntityRO(GetEntityDTO input) {

        RegisterEntityRO result = new RegisterEntityRO();
        Entity source = input.getEntity();

        List<Relation> relationDefs = input.getOutgoingRelations();

        result.setGroupName(source.getGroupName());
        result.setValidityPeriod(PeriodBoundaryConverter.to(source.getValidityPeriod()));

        toAbstractEntityData(source, result);

        result.getSimpleAttributes().addAll(toSimpleAttrs(source.getSimpleAttribute(), source.getName()));
        result.getArrayAttributes().addAll(toArrayAttrs(source.getArrayAttribute(), source.getName()));
        result.getComplexAttributes().addAll(to(source.getComplexAttribute(), source.getName()));

        toAttributeGroups(source.getAttributeGroups(), result.getAttributeGroups());
        toRelationGroups(source.getRelationGroups(), result.getRelationGroups());

        result.setDashboardVisible(source.isDashboardVisible());
        result.setMergeSettings(MergeSettingsConverter.to(source.getMergeSettings()));
        result.setExternalIdGenerationStrategy(ExternalIdGenerationStrategyConverter.to(source.getExternalIdGenerationStrategy()));

        List<RelationRO> targetRelationDefs = new ArrayList<>();
        for (Relation relationDef : relationDefs) {
            targetRelationDefs.add(RelationDefConverter.convert(relationDef));
        }

        result.setRelations(targetRelationDefs);
        return result;
    }

    /**
     * Convert Model object to Request object.
     *
     * @param source - will be used for filling
     * @param target - will be filled
     */
    private static void toAttributeGroups(List<AttributeGroup> source, List<GroupsRO> target) {
        for (AttributeGroup attributeGroup : source) {
            GroupsRO attributeGroupRo = new GroupsRO()
                .withColumn(attributeGroup.getColumn())
                .withRow(attributeGroup.getRow())
                .withTitle(attributeGroup.getTitle())
                .withAttributes(attributeGroup.getAttributes());

            attributeGroupRo.setCustomProperties(CustomPropertiesConverter.to(attributeGroup.getCustomProperties()));
            attributeGroupRo.setDefaultGroup(attributeGroup.isDefaultGroup());

            target.add(attributeGroupRo);
        }
    }

    /**
     * Convert Model object to Request object.
     *
     * @param source - will be used for filling
     * @param target - will be filled
     */
    private static void toRelationGroups(Collection<RelationGroup> source, List<RelationGroupsRO> target) {
        for (RelationGroup relationGroup : source) {
            RelationGroupsRO relationGroupRo = new RelationGroupsRO()
                .withColumn(relationGroup.getColumn())
                .withRow(relationGroup.getRow())
                .withTitle(relationGroup.getTitle())
                .withRelType(RelationTypeRO.valueOf(relationGroup.getRelType().name()))
                .withRelations(relationGroup.getRelations());
            target.add(relationGroupRo);
        }
    }

    /**
     * Converts a REST entity definition DTO into internal format.
     *
     * @param source REST DTO
     * @return request context
     */
    public static UpsertDataModelContextBuilder from(RegisterEntityRO source, long draftId) {

        Entity result = new Entity();
        Map<String, NestedEntity> nestedEntities = new HashMap<>();

        AbstractEntityDefinitionConverter.fromAbstractEntityData(source, result);
        SimpleAttributeDefConverter.copySimpleAttributeDataList(source.getSimpleAttributes(), result.getSimpleAttribute());
        ArrayAttributeDefConverter.copySimpleAttributeDataList(source.getArrayAttributes(), result.getArrayAttribute());
        AbstractEntityDefinitionConverter.fromComplexAttributeDataList(source.getComplexAttributes(), result.getComplexAttribute());

        result.setDashboardVisible(source.isDashboardVisible());
        result.setGroupName(source.getGroupName());
        result.setValidityPeriod(PeriodBoundaryConverter.from(source.getValidityPeriod()));
        result.setMergeSettings(MergeSettingsConverter.from(source.getMergeSettings()));
        result.setExternalIdGenerationStrategy(ExternalIdGenerationStrategyConverter.from(source.getExternalIdGenerationStrategy()));

        if (source.getAttributeGroups() != null) {
            fromAttributeGroups(source.getAttributeGroups(), result.getAttributeGroups());
        }

        if (source.getRelationGroups() != null) {
            fromRelationGroups(source.getRelationGroups(), result.getRelationGroups());
        }

        List<RelationRO> sourceDefs = source.getRelations();
        List<Relation> targetDefs = new ArrayList<>();

        for (RelationRO relationDefinition : sourceDefs) {
            targetDefs.add(RelationDefinitionConverter.convert(relationDefinition));
        }

        return UpsertDataModelContext.builder()
            .entitiesUpdate(Collections.singletonList(result))
            .nestedEntitiesUpdate(new ArrayList<>(nestedEntities.values()))
            .relationsUpdate(targetDefs)
            .draftId(draftId > 0 ? draftId : null);
    }

    public static Entity toEntity(RegisterEntityRO source) {

        Entity result = new Entity();

        AbstractEntityDefinitionConverter.fromAbstractEntityData(source, result);
        SimpleAttributeDefConverter.copySimpleAttributeDataList(source.getSimpleAttributes(), result.getSimpleAttribute());
        ArrayAttributeDefConverter.copySimpleAttributeDataList(source.getArrayAttributes(), result.getArrayAttribute());
        AbstractEntityDefinitionConverter.fromComplexAttributeDataList(source.getComplexAttributes(), result.getComplexAttribute());

        result.setDashboardVisible(source.isDashboardVisible());
        result.setGroupName(source.getGroupName());
        result.setValidityPeriod(PeriodBoundaryConverter.from(source.getValidityPeriod()));
        result.setMergeSettings(MergeSettingsConverter.from(source.getMergeSettings()));
        result.setExternalIdGenerationStrategy(ExternalIdGenerationStrategyConverter.from(source.getExternalIdGenerationStrategy()));

        if (source.getAttributeGroups() != null) {
            fromAttributeGroups(source.getAttributeGroups(), result.getAttributeGroups());
        }

        if (source.getRelationGroups() != null) {
            fromRelationGroups(source.getRelationGroups(), result.getRelationGroups());
        }

        return result;
    }

    /**
     * Convert Request object to Model object.
     *
     * @param sourceAttributeGroups - will be used for filling
     * @param targetAttributeGroups - will be filled
     */
    private static void fromAttributeGroups(List<GroupsRO> sourceAttributeGroups, List<AttributeGroup> targetAttributeGroups) {
        for (GroupsRO attributeGroup : sourceAttributeGroups) {
            AttributeGroup attributeGroupDef = new AttributeGroup()
                .withColumn(attributeGroup.getColumn())
                .withRow(attributeGroup.getRow())
                .withTitle(attributeGroup.getTitle())
                .withAttributes(attributeGroup.getAttributes())
                .withDefaultGroup(attributeGroup.isDefaultGroup())
                .withCustomProperties(CustomPropertiesConverter.from(attributeGroup.getCustomProperties()));

            targetAttributeGroups.add(attributeGroupDef);
        }
    }

    /**
     * Convert Request object to Model object.
     *
     * @param sourceRelationGroups - will be used for filling
     * @param targetRelationGroups - will be filled
     */
    private static void fromRelationGroups(List<RelationGroupsRO> sourceRelationGroups, List<RelationGroup> targetRelationGroups) {
        for (RelationGroupsRO relationGroup : sourceRelationGroups) {
            RelationGroup relationGroupDef = new RelationGroup()
                .withColumn(relationGroup.getColumn())
                .withRow(relationGroup.getRow())
                .withTitle(relationGroup.getTitle())
                .withRelType(RelType.fromValue(relationGroup.getRelType()))
                .withRelations(relationGroup.getRelations());
            targetRelationGroups.add(relationGroupDef);
        }
    }
}
